
#include <BScene>
#include <BNode>
#include <BMesh>

#include <BPickItem>
#include <BPicker>
#include "member_BPicker.h"
#include "member_BNode.h"

using namespace BWE;

#define member						(*(member_BPicker*)_ptr)
#define member_allocate()			_ptr = new member_BPicker(this)
#define member_release()			delete (member_BPicker*)_ptr

BPicker::BPicker()
{
	member_allocate();
}
BPicker::BPicker(const BLine& line)
{
	member_allocate();
	this->setLine(line);
}
BPicker::~BPicker()
{
	member_release();
}

void BPicker::setLine(const BLine& line)
{
	member.line = line;
}
const BLine& BPicker::line() const
{
	return member.line;
}

bool BPicker::exclude(BObject* object)
{
	if (object && !member.exclusions.contain(object))
	{
		return member.exclusions.append(object);
	}
	return false;
}
bool BPicker::include(BObject* object)
{
	if (!object)
		return false;
	return member.exclusions.remove(object);
}

int BPicker::exclusionCount() const
{
	return member.exclusions.size();
}
BObject* BPicker::exclusion(int index) const
{
	return member.exclusions(index);
}

void BPicker::clearExclusions()
{
	member.exclusions.clear();
}

BPickItem* BPicker::apply(BScene* scene)
{
	member.items.clear();
	for (int i = 0; i < scene->nodeCount(); i++)
	{
		BNode* node = scene->node(i);
		pick(member.line, node);
	}
	return member.items.first();
}
BPickItem* BPicker::apply(BNode* node)
{
	member.items.clear();
	pick(member.line, node);
	return member.items.first();
}

int BPicker::itemCount() const
{
	return member.items.size();
}
BPickItem* BPicker::item(int index) const
{
	return member.items(index);
}

void BPicker::addItem(BPickItem* item)
{
	if (item)
	{
		item->setNode(member.node);
		for(int i = 0; i < member.items.size(); i++)
		{
			if (member.items[i]->ratio() > item->ratio())
			{
				member.items.insert(i, item);
				return;
			}
		}
		member.items.append(item);
	}
}

void BPicker::pick(const BLine& line, BNode* node)
{
	if (node->visible() == false)
		return;
	if (member.exclusions.contain(node))
		return;

	BMatrix matrix = node->matrix();
	matrix.inverse();
	BLine localLine = line * matrix;

	member.node = node;
	for (int i = 0; i < node->shapeCount(); i++)
	{
		if (node_member(node)->shapeItems[i].visible == false)
			continue;
		BShape* shape = node->shape(i);
		if (member.exclusions.contain(shape))
			continue;
		pick(localLine, shape);
	}
	return;
}
void BPicker::pick(const BLine& line, BShape* shape)
{
	if (BSphere* sphere = dynamic_cast<BSphere*>(shape))
	{
		pick(line, sphere);
		return;
	}
	if (BGeoSphere* geosphere = dynamic_cast<BGeoSphere*>(shape))
	{
		pick(line, geosphere);
		return;
	}
	if (BCylinder* cylinder = dynamic_cast<BCylinder*>(shape))
	{
		return pick(line, cylinder);
	}
	if (BMesh* mesh = dynamic_cast<BMesh*>(shape))
	{
		return pick(line, mesh);
	}
	if (BGeometry* geometry = dynamic_cast<BGeometry*>(shape))
	{
		return pick(line, geometry);
	}

}
void BPicker::pick(const BLine& line, BMesh* mesh)
{
	BVectorArray* vertices = mesh->vertices();
	if (!vertices)
		return;
	BFaceArray* faces = mesh->faces();
	for (int i = 0; i < faces->size(); i++)
	{
		int ia = (*faces)[i].a();
		int ib = (*faces)[i].b();
		int ic = (*faces)[i].c();

		BVector va = (*vertices)[ia];
		BVector vb = (*vertices)[ib];
		BVector vc = (*vertices)[ic];

		float ratio;
		if (line.intersect(va, vb, vc, ratio))
		{
			BVector normal = (vb - va).cross(vc - va);
			normal.normalize();
			BPickItem* item = new BPickItem();
			item->setRatio(ratio);
			item->setShape(mesh);
			item->setNormal(normal);
			item->setIndex(i);
			item->setPoint(line.vertex(ratio));
			addItem(item);
			return;
		}
	}
}
void BPicker::pick(const BLine& line, BGeometry* geometry)
{
	BVectorArray* vertices = geometry->vertices();
	if (!vertices)
		return;
	for (int i = 0; i < geometry->primitiveCount(); i++)
	{
		BPrimitive* primitive = geometry->primitive(i);
		BIndexArray* indices = primitive->indices();
		int first = primitive->first();
		int count = primitive->count();
		if (indices)
		{
			if (primitive->mode() == BPrimitive::Triangles)
			{
				for (int i = first; i < count; i += 3)
				{
					int ia = (*indices)[i];
					int ib = (*indices)[i + 1];
					int ic = (*indices)[i + 2];
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					float ratio;
					if (line.intersect(va, vb, vc, ratio))
					{
						BVector normal = (vb - va).cross(vc - va);
						normal.normalize();
						BPickItem* item = new BPickItem();
						item->setRatio(ratio);
						item->setShape(geometry);
						item->setPoint(line.vertex(ratio));
						item->setNormal(normal);
						item->setIndex(i);
						item->setPrimitive(primitive);
						addItem(item);
						return;
					}
				}
			}
			if (primitive->mode() == BPrimitive::TriangleStrip)
			{
				for (int i = first; i < count; i++)
				{
					int ia = (*indices)[i];
					int ib = (*indices)[i + 1];
					int ic = (*indices)[i + 2];
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					float ratio;
					if (line.intersect(va, vb, vc, ratio))
					{
						BVector normal = (vb - va).cross(vc - va);
						normal.normalize();
						BPickItem* item = new BPickItem();
						item->setRatio(ratio);
						item->setShape(geometry);
						item->setPoint(line.vertex(ratio));
						item->setNormal(normal);
						item->setIndex(i);
						item->setPrimitive(primitive);
						addItem(item);
						return;
					}
				}
			}
			if (primitive->mode() == BPrimitive::Quads)
			{
				for (int i = first; i < count; i += 4)
				{
					int ia = (*indices)[i];
					int ib = (*indices)[i + 1];
					int ic = (*indices)[i + 2];
					int id = (*indices)[i + 3];
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					const BVector& vd = (*vertices)[id];
					float ratio = 0;
					if (line.intersect(va, vb, vc, ratio))
					{
						BVector normal = (vb - va).cross(vc - va);
						normal.normalize();
						BPickItem* item = new BPickItem();
						item->setRatio(ratio);
						item->setShape(geometry);
						item->setPoint(line.vertex(ratio));
						item->setNormal(normal);
						item->setIndex(i);
						item->setPrimitive(primitive);
						addItem(item);
						return;
					}
					if (line.intersect(va, vc, vd, ratio))
					{
						BVector normal = (vb - va).cross(vc - va);
						normal.normalize();
						BPickItem* item = new BPickItem();
						item->setRatio(ratio);
						item->setShape(geometry);
						item->setPoint(line.vertex(ratio));
						item->setNormal(normal);
						item->setIndex(i);
						item->setPrimitive(primitive);
						addItem(item);
						return;
					}
				}
			}
		}
		else
		{
			if (primitive->mode() == BPrimitive::Triangles)
			{
				for (int i = first; i < count; i += 3)
				{
					const BVector& va = (*vertices)[i];
					const BVector& vb = (*vertices)[i + 1];
					const BVector& vc = (*vertices)[i + 2];
					float ratio;
					if (line.intersect(va, vb, vc, ratio))
					{
						BVector normal = (vb - va).cross(vc - va);
						normal.normalize();
						BPickItem* item = new BPickItem();
						item->setRatio(ratio);
						item->setShape(geometry);
						item->setPoint(line.vertex(ratio));
						item->setNormal(normal);
						item->setIndex(i);
						item->setPrimitive(primitive);
						addItem(item);
						return;
					}
				}
			}
			if (primitive->mode() == BPrimitive::TriangleStrip)
			{
				for (int i = first; i < count; i++)
				{
					const BVector& va = (*vertices)[i];
					const BVector& vb = (*vertices)[i + 1];
					const BVector& vc = (*vertices)[i + 2];
					float ratio;
					if (line.intersect(va, vb, vc, ratio))
					{
						BVector normal = (vb - va).cross(vc - va);
						normal.normalize();
						BPickItem* item = new BPickItem();
						item->setRatio(ratio);
						item->setShape(geometry);
						item->setPoint(line.vertex(ratio));
						item->setNormal(normal);
						item->setIndex(i);
						item->setPrimitive(primitive);
						addItem(item);
						return;
					}
				}
			}
			if (primitive->mode() == BPrimitive::Quads)
			{
				for (int i = first; i < count; i += 4)
				{
					const BVector& va = (*vertices)[i];
					const BVector& vb = (*vertices)[i + 1];
					const BVector& vc = (*vertices)[i + 2];
					const BVector& vd = (*vertices)[i + 3];
					float ratio = 0;
					if (line.intersect(va, vb, vc, ratio))
					{
						BVector normal = (vb - va).cross(vc - va);
						normal.normalize();
						BPickItem* item = new BPickItem();
						item->setRatio(ratio);
						item->setShape(geometry);
						item->setPoint(line.vertex(ratio));
						item->setNormal(normal);
						item->setIndex(i);
						item->setPrimitive(primitive);
						addItem(item);
						return;
					}
					if (line.intersect(va, vc, vd, ratio))
					{
						BVector normal = (vb - va).cross(vc - va);
						normal.normalize();
						BPickItem* item = new BPickItem();
						item->setRatio(ratio);
						item->setShape(geometry);
						item->setPoint(line.vertex(ratio));
						item->setNormal(normal);
						item->setIndex(i);
						item->setPrimitive(primitive);
						addItem(item);
						return;
					}
				}
			}
		}
	}
}
void BPicker::pick(const BLine& line, BSphere* sphere)
{
	BVector center = member.node->shapeMatrix(sphere).position();
	BVector perp = line.perpVertex(center);
	float radius = sphere->radius();
	float dist = (perp - center).length();
	if (dist < radius)
	{
		float len = sqrt(radius * radius - dist * dist);
		BVector point = perp - line.vector() * len;
		float ratio = (point - line.start()).length() / line.length();
		BVector normal = point - center;
		normal.normalize();
		BPickItem* item = new BPickItem();
		item->setRatio(ratio);
		item->setShape(sphere);
		item->setPoint(point);
		item->setNormal(normal);
		addItem(item);
		return;

	}
}
void BPicker::pick(const BLine& line, BGeoSphere* geosphere)
{
	BVector perp = line.perpVertex(BVector());
	float radius = geosphere->radius();
	float dist = perp.length();
	if (dist < radius)
	{
		float len = sqrt(radius * radius - dist * dist);
		BVector point = perp - line.vector() * len;
		float ratio = (point - line.start()).length() / line.length();
		BVector normal = point;
		normal.normalize();
		BPickItem* item = new BPickItem();
		item->setRatio(ratio);
		item->setShape(geosphere);
		item->setPoint(point);
		item->setNormal(normal);
		addItem(item);
		return;
	}
}
void BPicker::pick(const BLine& line, BCylinder* cylinder)
{
	pick(line, (BGeometry*)cylinder);
}

